

%{
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright 2011 Nexenta Systems, Inc. All rights reserved.
 * Copyright (c) 2017 by Delphix. All rights reserved.
 * Copyright 2019, Joyent, Inc.
 * Copyright 2021 Oxide Computer Company
 */

#include <sys/types.h>
#include <sys/isa_defs.h>

%}

%option reentrant
%option always-interactive
%option bison-bridge
%option bison-locations
%option noinput
%option nounput
%option noyyalloc
%option noyyrealloc
%option noyyfree
%option noyywrap
%option yylineno
%option debug
%option array
%option nounistd
%option extra-type="struct mdb_frame *"

%{

#include <strings.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

#include <mdb/mdb_types.h>
#include <mdb/mdb_debug.h>
#include <mdb/mdb_nv.h>
#include <mdb/mdb_frame.h>
#include <mdb/mdb_string.h>
#include <mdb/mdb_stdlib.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb.h>

#include <mdb/mdb_lex.h>

static int yyinput(yyscan_t scanner);
static void yyoutput(int c, yyscan_t scanner);
static void bkls_destroy(mdb_frame_t *);

void yy_flush_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner );

/*
 * lex hardcodes yyin and yyout to stdin and stdout, respectively, before we get
 * control.  We've redirected printf and fprintf (see mdb_lex.h) to yyprintf and
 * yyfprintf, which ignore the FILE * provided by yyout.  __iob-based stdin and
 * stdout are useless in kmdb, since we don't have stdio.  We define __iob here
 * to shut the linker up.
 */
#ifdef _KMDB
FILE __iob[_NFILE];
#endif

#define YY_USER_INIT	do { \
		BEGIN(S_INITIAL); \
	} while (0)

#define YY_INPUT(buf, result, max_size) do { \
		int c = yyinput(yyscanner); \
		result = (c) ? (buf[0] = c, 1) : YY_NULL; \
	} while (0)

#define ECHO \
	do { \
		int i; \
		for (i = 0; i < yyleng; i++) \
			yyoutput(yytext[i], yyscanner); \
	} while (0)

static void string_unquote(char *, yyscan_t );

%}

%o	9000
%a	5000

%s	S_SHELLCMD
%s	S_INITIAL
%s	S_FMTLIST
%s	S_ARGLIST
%s	S_EXPR

RGX_CMD_CHAR	[?%@A-Z\^_`a-z]
RGX_SYMBOL	[a-zA-Z_.][0-9a-zA-Z_.`]*
RGX_SIMPLE_CHAR	[^ \t\n;!|"'\$]
RGX_CHR_SEQ	([^'\n]|\\[^'\n]|\\')*
RGX_STR_SEQ	([^"\\\n]|\\[^"\n]|\\\")*
RGX_COMMENT	"//".*\n

%%

<S_INITIAL>{RGX_COMMENT}	|
<S_FMTLIST>{RGX_COMMENT}	|
<S_ARGLIST>{RGX_COMMENT}	{
		/*
		 * Comments are legal in these three states -- if we see one
		 * eat the line and return the newline character.
		 */
		BEGIN(S_INITIAL);
		return ('\n');
	}

<S_INITIAL>"=="	|
<S_EXPR>"=="	return (MDB_TOK_EQUAL); /* Equality operator */

<S_INITIAL>"!="	|
<S_EXPR>"!="	return (MDB_TOK_NOTEQUAL); /* Inequality operator */

<S_INITIAL>"%%"	|
<S_EXPR>"%%"	return (MDB_TOK_MODULUS); /* Modulus operator */

<S_INITIAL>"!"	|
<S_FMTLIST>"!"	|
<S_ARGLIST>"!"	{
		/*
		 * Shell escapes are legal in all of these states -- switch to
		 * the shell command state and return the ! character.
		 */
		BEGIN(S_SHELLCMD);
		return (yytext[0]);
	}

<S_FMTLIST>"|"	|
<S_ARGLIST>"|"	{
		/*
		 * Pipelines can appear in any of these states -- switch to
		 * the initial state and return the | character.
		 */
		BEGIN(S_INITIAL);
		return (yytext[0]);
	}

<S_SHELLCMD>[^;\n]+	{
		/*
		 * Once in the shell-command state, we return all remaining
		 * characters up to a newline or ';' delimiter as a single
		 * string which will be passed to $SHELL -c.
		 */
		yylval->l_string = strdup(yytext);
		BEGIN(S_INITIAL);
		return (MDB_TOK_STRING);
	}

<S_INITIAL>"::"{RGX_SYMBOL}	{
		/*
		 * Verb ::command-name -- lookup the correspond dcmd and
		 * switch to the argument list state.
		 */
		if ((yylval->l_dcmd = mdb_dcmd_lookup(yytext + 2)) == NULL)
			yyperror(yyget_lloc(yyscanner), yyscanner,
			    "invalid command '%s'", yytext);

		BEGIN(S_ARGLIST);
		return (MDB_TOK_DCMD);
	}

<S_INITIAL>"$<<"|"$<"|"$>"	|
<S_INITIAL>[\$:]{RGX_CMD_CHAR}	{
		/*
		 * Old-style :c or $c command -- lookup the corresponding dcmd
		 * and switch to the argument list state.
		 */
		if ((yylval->l_dcmd = mdb_dcmd_lookup(yytext)) == NULL)
			yyperror(yyget_lloc(yyscanner), yyscanner,
			    "invalid command '%s'", yytext);

		BEGIN(S_ARGLIST);
		return (MDB_TOK_DCMD);
	}

<S_INITIAL>">/"[a-zA-Z0-9]"/"	{
		/*
		 * Variable assignment with size cast -- append the cast letter
		 * to the argument list, and switch to the argument list state.
		 */
		mdb_arg_t arg;

		arg.a_un.a_char = yytext[2];
		arg.a_type = MDB_TYPE_CHAR;

		mdb_argvec_append(&yyextra->f_argvec, &arg);
		yylval->l_dcmd = mdb_dcmd_lookup(">");

		BEGIN(S_ARGLIST);
		return (MDB_TOK_DCMD);
	}

<S_INITIAL>">"	{
		/*
		 * Variable assignment -- switch to the argument list state.
		 */
		yylval->l_dcmd = mdb_dcmd_lookup(yytext);
		BEGIN(S_ARGLIST);
		return (MDB_TOK_DCMD);
	}

<S_INITIAL>[/\\?][ \t]*[vwzWZlLM]	{
		/*
		 * Format verb followed by write or match signifier -- switch
		 * to the value list state and return the verb character.  We
		 * also append the actual format character to the arg list.
		 */
		mdb_arg_t arg;

		arg.a_un.a_char = yytext[yyleng - 1];
		arg.a_type = MDB_TYPE_CHAR;

		mdb_argvec_append(&yyextra->f_argvec, &arg);

		BEGIN(S_ARGLIST);
		return yytext[0];
	}

<S_INITIAL>[/\\@?=]	{
		/*
		 * Format verb -- switch to the format list state and return
		 * the actual verb character verbatim.
		 */
		BEGIN(S_FMTLIST);
		return (yytext[0]);
	}

<S_INITIAL>'{RGX_CHR_SEQ}$	{
		yyerror(yyget_lloc(yyscanner), yyscanner, "syntax error: ' unmatched");
	}

<S_EXPR>'{RGX_CHR_SEQ}$		{
		yyerror(yyget_lloc(yyscanner), yyscanner, "syntax error: ' unmatched");
	}

<S_INITIAL>'{RGX_CHR_SEQ}'	|
<S_EXPR>'{RGX_CHR_SEQ}'		{
		char *s;
#ifdef _LITTLE_ENDIAN
		char *p, *q;
#endif
		size_t nbytes;

		/*
		 * If the character sequence is zero-length, return 0.
		 */
		if (yyleng == 2) {
			yylval->l_immediate = 0;
			return (MDB_TOK_IMMEDIATE);
		}

		s = yytext + 1;			/* Skip past initial quote */
		yytext[yyleng - 1] = '\0';	/* Overwrite final quote */
		nbytes = stresc2chr(s);		/* Convert escapes */
		yylval->l_immediate = 0;	/* Initialize token value */

		if (nbytes > sizeof (uintmax_t)) {
			yyerror(yyget_lloc(yyscanner), yyscanner,
			    "character constant may not exceed %lu bytes\n",
			    (ulong_t)sizeof (uintmax_t));
		}

#ifdef _LITTLE_ENDIAN
		p = ((char*)&yylval->l_immediate) + nbytes - 1;

		for (q = s; nbytes != 0; nbytes--)
			*p-- = *q++;
#else
		bcopy(s, ((char *)&yylval->l_immediate) +
		    sizeof (uintmax_t) - nbytes, nbytes);
#endif
		return (MDB_TOK_IMMEDIATE);
	}

\"{RGX_STR_SEQ}$	{
		yyerror(yyget_lloc(yyscanner), yyscanner,
		    "syntax error: \" unmatched");
	}

\"{RGX_STR_SEQ}\"	{
		/*
		 * Quoted string -- convert C escape sequences and return the
		 * string as a token.
		 */
		yylval->l_string = strndup(yytext + 1, yyleng - 2);
		(void) stresc2chr(yylval->l_string);
		return (MDB_TOK_STRING);
	}

<S_ARGLIST>"$["	|
<S_FMTLIST>"$["	{
		/*
		 * Start of expression -- begin expression state and save the
		 * current state so we can return at the end of the expression.
		 */
		yyextra->f_oldstate = YYSTATE;
		BEGIN(S_EXPR);
		return (MDB_TOK_LEXPR);
	}

<S_ARGLIST>{RGX_SIMPLE_CHAR}*("'"{RGX_CHR_SEQ}"'"|\"{RGX_STR_SEQ}\"|{RGX_SIMPLE_CHAR}+)* {
		/*
		 * String token -- create a copy of the string and return it.
		 * We need to handle embedded single and double-quote pairs,
		 * which overcomplicates this slightly.
		 */
		yylval->l_string = strdup(yytext);
		string_unquote(yylval->l_string, yyscanner);
		return (MDB_TOK_STRING);
	}

<S_FMTLIST>[0-9]+	{
		/*
		 * Immediate value -- in the format list, all immediates
		 * are assumed to be in decimal.
		 */
		yylval->l_immediate = mdb_strtonum(yytext, 10);
		return (MDB_TOK_IMMEDIATE);
	}

<S_FMTLIST>{RGX_SIMPLE_CHAR}	{
		/*
		 * Non-meta character -- in the format list, we return each
		 * character as a separate token to be added as an argument.
		 */
		yylval->l_char = yytext[0];
		return (MDB_TOK_CHAR);
	}

<S_EXPR>";"|"!"|\n	{
		/*
		 * In the expression state only, we cannot see a command
		 * delimiter or shell escape before we end the expression.
		 */
		yyerror(yyget_lloc(yyscanner), yyscanner,
		    "syntax error: $[ unmatched");
	}

<S_EXPR>"]"	{
		/*
		 * End of expression state.  Restore the state we were in
		 * before the "$[" which started this expression.
		 */
		BEGIN(yyextra->f_oldstate);
		return (MDB_TOK_REXPR);
	}

<S_INITIAL>"<"{RGX_SYMBOL}	|
<S_INITIAL>"<"[0-9]		|
<S_EXPR>"<"{RGX_SYMBOL}		|
<S_EXPR>"<"[0-9]	{
		/*
		 * Variable reference -- lookup the variable and return a
		 * pointer to it.  Referencing undefined variables is an error.
		 */
		yylval->l_var = mdb_nv_lookup(&mdb.m_nv, &yytext[1]);

		if (yylval->l_var == NULL)
			yyerror(yyget_lloc(yyscanner), yyscanner,
			    "variable '%s' is not defined", &yytext[1]);

		return (MDB_TOK_VAR_REF);
	}

<S_INITIAL>"<<"	|
<S_EXPR>"<<"	return (MDB_TOK_LSHIFT); /* Logical shift left operator */

<S_INITIAL>">>"	|
<S_EXPR>">>"	return (MDB_TOK_RSHIFT); /* Logical shift right operator */

<S_INITIAL>"*/"[a-zA-Z0-9]"/"	|
<S_EXPR>"*/"[a-zA-Z0-9]"/"	{
		switch (yytext[2]) {
			case 'c': case '1':
				return (MDB_TOK_COR1_DEREF);
			case 's': case '2':
				return (MDB_TOK_COR2_DEREF);
			case 'i': case '4':
#ifdef _ILP32
			case 'l':
#endif
				return (MDB_TOK_COR4_DEREF);
#ifdef _LP64
			case 'l':
#endif
			case '8':
				return (MDB_TOK_COR8_DEREF);
		}
		yyerror(yyget_lloc(yyscanner), yyscanner,
			"invalid cast -- %s\n", yytext);
	}

<S_INITIAL>"%/"[a-zA-Z0-9]"/"	|
<S_EXPR>"%/"[a-zA-Z0-9]"/"	{
		switch (yytext[2]) {
			case 'c': case '1':
				return (MDB_TOK_OBJ1_DEREF);
			case 's': case '2':
				return (MDB_TOK_OBJ2_DEREF);
			case 'i': case '4':
#ifdef _ILP32
			case 'l':
#endif
				return (MDB_TOK_OBJ4_DEREF);
#ifdef _LP64
			case 'l':
#endif
			case '8':
				return (MDB_TOK_OBJ8_DEREF);
		}
		yyerror(yyget_lloc(yyscanner), yyscanner,
			"invalid cast -- %s\n", yytext);
	}

<S_INITIAL>0[iI][0-1][0-1_]*	|
<S_EXPR>0[iI][0-1][0-1_]*	{
		/*
		 * Binary immediate value.
		 */
		yylval->l_immediate = mdb_strtonum(yytext + 2, 2);
		return (MDB_TOK_IMMEDIATE);
	}

<S_INITIAL>0[oO][0-7][0-7_]*	|
<S_EXPR>0[oO][0-7][0-7_]*	{
		/*
		 * Octal immediate value.
		 */
		yylval->l_immediate = mdb_strtonum(yytext + 2, 8);
		return (MDB_TOK_IMMEDIATE);
	}

<S_INITIAL>0[tT][0-9][0-9_]*"."[0-9][0-9_]*	|
<S_EXPR>0[tT][0-9][0-9_]*"."[0-9][0-9_]*	{
#ifdef _KMDB
		yyerror(yyget_lloc(yyscanner), yyscanner,
			"floating point not supported\n");
#else
		/*
		 * Decimal floating point value.
		 */
		char *p, c;
		double d;
		int i;

		if ((p = strsplit(yytext, '.')) == NULL)
			yyerror(yyget_lloc(yyscanner), yyscanner,
			    "internal scanning error -- expected '.'\n");

		d = (double)mdb_strtonum(yytext + 2, 10);

		i = 0;
		while (*p != '\0') {
			c = *p++;
			if (c == '_')
				continue;
			d = d * 10 + c - '0';
			i++;
		}

		while (i-- != 0)
			d /= 10;

                yylval->l_immediate = *((uintmax_t *)&d);
		return (MDB_TOK_IMMEDIATE);
#endif
	}

<S_INITIAL>0[tT][0-9][0-9_]*	|
<S_EXPR>0[tT][0-9][0-9_]*	{
		/*
		 * Decimal immediate value.
		 */
		yylval->l_immediate = mdb_strtonum(yytext + 2, 10);
		return (MDB_TOK_IMMEDIATE);
	}

<S_INITIAL>0[xX][0-9a-fA-F][0-9a-fA-F_]*	|
<S_EXPR>0[xX][0-9a-fA-F][0-9a-fA-F_]*	{
		/*
		 * Hexadecimal value.
		 */
		yylval->l_immediate = mdb_strtonum(yytext + 2, 16);
		return (MDB_TOK_IMMEDIATE);
	}

<S_INITIAL>[0-9a-fA-F][0-9a-fA-F_]*	|
<S_EXPR>[0-9a-fA-F][0-9a-fA-F_]*	{
		GElf_Sym sym;
		/*
		 * Immediate values without an explicit base are converted
		 * using the default radix (user configurable).  However, if
		 * the token does *not* begin with a digit, it is also a
		 * potential symbol (e.g. "f") so we have to check that first.
		 */
		if (strchr("0123456789", yytext[0]) == NULL &&
		    mdb_tgt_lookup_by_name(mdb.m_target,
		    MDB_TGT_OBJ_EVERY, yytext, &sym, NULL) == 0)
			yylval->l_immediate = (uintmax_t)sym.st_value;
		else
			yylval->l_immediate = mdb_strtonum(yytext, mdb.m_radix);
		return (MDB_TOK_IMMEDIATE);
	}

<S_INITIAL>{RGX_SYMBOL}	|
<S_EXPR>{RGX_SYMBOL}	{
		/*
		 * Symbol -- parser will look up in symbol table.
		 */
		yylval->l_string = strdup(yytext);
		return (MDB_TOK_SYMBOL);
	}

";"|\n	{
		/*
		 * End of command -- return to start state and return literal.
		 */
		BEGIN(S_INITIAL);
		return (yytext[0]);
	}

[ \t]	;			/* Ignore whitespace */

.	return (yytext[0]);	/* Return anything else */

%%

void
mdb_lex_debug(int i)
{
	yydebug = i;
}

void
mdb_lex_state_yydiscard(mdb_frame_t *f)
{
	int c;

	/*
	 * If stdin is a string, pipeline, or tty, throw away all our buffered
	 * data. Otherwise discard characters up to the next likely delimiter.
	 */
	if (mdb_iob_isastr(mdb.m_in) || mdb_iob_isatty(mdb.m_in) ||
	    mdb_iob_isapipe(mdb.m_in))
		mdb_iob_discard(mdb.m_in);
	else {
		while ((c = mdb_iob_getc(mdb.m_in)) != (int)EOF) {
			if (c == ';' || c == '\n')
				break;
		}
	}

	/*
	 * Update scanner, stdin is hardcoded in scanner but ignored
	 * due to overridedn input/unput/output routines
	 */
	yyrestart(stdin, f->f_lstate->scanner);

}

void
mdb_lex_undo(mdb_frame_t *f)
{
	yyscan_t yyscanner = f->f_lstate->scanner;
	struct yyguts_t *yyg = (struct yyguts_t*)yyscanner;
	yyg->yy_c_buf_p -= 1;
}

/*PRINTFLIKE2*/
/*ARGSUSED*/
int
yyfprintf(FILE *stream, const char *format, ...)
{
	va_list alist;

	va_start(alist, format);
	mdb_iob_vprintf(mdb.m_err, format, alist);
	va_end(alist);
	return (0);
}

/*PRINTFLIKE1*/
int
yyprintf(const char *format, ...)
{
	va_list alist;

	va_start(alist, format);
	mdb_iob_vprintf(mdb.m_err, format, alist);
	va_end(alist);
	return (0);
}

static int
yyinput(yyscan_t scanner)
{
	mdb_frame_t *fp;
	int c;

	fp = yyget_extra(scanner);
	c = mdb_iob_getc(mdb.m_in);

	if (c == EOF) {
		if (fp->f_lstate->eof_seen)
			return (0);

		fp->f_lstate->eof_seen = 1;
		c = '\n';
	} else
		fp->f_lstate->eof_seen = 0;

	if (c == '\n') {
		int lineno = yyget_lineno(scanner);
		yyset_lineno(++lineno, scanner);
	}

	return (c == EOF ? 0 : c);
}

__attribute__((unused))
static void
yyunput(int c, char *pyytext, yyscan_t scanner)
{
	if (c == '\n') {
		int lineno = yyget_lineno(scanner);
		yyset_lineno(--lineno, scanner);
	}

	(void) mdb_iob_ungetc(mdb.m_in, c == 0 ? EOF : c);
}

__attribute__((unused))
static void
yyoutput(int c, yyscan_t scanner)
{
	char ch = c;
	mdb_iob_nputs(mdb.m_out, &ch, sizeof (ch));
}

static char *
string_nextquote(char *s, char q1, char q2)
{
	char c = 0;

	do {
		if (c != '\\' && (*s == q1 || *s == q2))
			return (s);
	} while ((c = *s++) != '\0');

	return (NULL);
}

static void
string_unquote(char *s, yyscan_t scanner)
{
	char *o, *p, *q, c;

	for (o = p = s; (p = string_nextquote(p, '\'', '"')) != NULL; o = p) {
		/*
		 * If the quote wasn't the first character, advance
		 * the destination buffer past what we skipped.
		 */
		if (p > o) {
			/* Using memmove to prevent possible overlap. */
			(void) memmove(s, o, p - o);
			s += p - o;
		}

		c = *p;	/* Save the current quote */

		/*
		 * Look ahead and find the matching quote.  If none is
		 * found, use yyerror to longjmp out of the lexer.
		 */
		if (c == '"')
			q = string_nextquote(p + 1, c, c);
		else
			q = strchr(p + 1, c);

		if (q == NULL)
			yyerror(yyget_lloc(scanner), scanner,
			    "syntax error: %c unmatched", c);

		/*
		 * If the string is non-empty, copy it to the destination
		 * and convert escape sequences if *p is double-quote.
		 */
		if (q > p + 1) {
			(void) memmove(s, p + 1, q - p - 1);
			if (c == '"') {
                                s[q - p - 1] = '\0';
				s += stresc2chr(s);
			} else
				s += q - p - 1;
		}

		p = q + 1; /* Advance p past matching quote */
	}

	(void) memmove(s, o, strlen(o) + 1);
}

void
mdb_lex_state_create(mdb_frame_t *f)
{
	f->f_lstate = mdb_alloc(sizeof (mdb_lex_state_t), UM_SLEEP);
	f->f_oldstate = 0;
	f->f_lstate->eof_seen = 0;
	f->f_lstate->allocs = NULL;
	f->f_lstate->parser = NULL;

	yylex_init_extra(f, &f->f_lstate->scanner);

	if (yydebug != 0)
		yyset_debug(yydebug, f->f_lstate->scanner);

	mdb_argvec_create(&f->f_argvec);
}

void
mdb_lex_state_destroy(mdb_frame_t *f)
{
	bkls_destroy(f);
	mdb_free(f->f_lstate, sizeof (mdb_lex_state_t));
	f->f_lstate = NULL;
	mdb_argvec_destroy(&f->f_argvec);
}

void
mdb_lex_state_yyparse(mdb_frame_t *fp)
{
	YYSTYPE *pyylv;
	YYLTYPE *pyylc;
	yypstate *pp;
	yyscan_t ps;

	pp = yypstate_new();
	fp->f_lstate->parser = pp;
	pyylv = &fp->f_lstate->pushed_value;
	pyylc = &fp->f_lstate->pushed_loc;
	ps = fp->f_lstate->scanner;

	while (yypush_parse(pp, yylex(pyylv, pyylc, ps), pyylv, pyylc, ps)
	    == YYPUSH_MORE)
		continue;

	yypstate_delete(pp);
	fp->f_lstate->parser = NULL;
}

static void
yyerror_reset(yyscan_t scanner)
{
	mdb_frame_t *fp;

	fp = yyget_extra(scanner);
	mdb_lex_state_yydiscard(fp);
	mdb_argvec_reset(&fp->f_argvec);
	longjmp(fp->f_pcb, MDB_ERR_PARSE);
}

static void
yywarn(yyscan_t scanner, const char *format, ...)
{
	va_list alist;
	char *s;

	mdb_iob_printf(mdb.m_err, "%s: ", mdb.m_pname);
	va_start(alist, format);
	mdb_iob_vprintf(mdb.m_err, format, alist);
	va_end(alist);

	if (strchr(format, '\n') == NULL) {
		if (!mdb_iob_isatty(mdb.m_in)) {
			mdb_iob_printf(mdb.m_err, " on line %d of %s",
			    yyget_lineno(scanner), mdb_iob_name(mdb.m_in));
		}

		s = strchr2esc(yyget_text(scanner), strlen(yyget_text(scanner)));
		mdb_iob_printf(mdb.m_err, " near \"%s\"\n", s);
		strfree(s);
	}
}

void
yyerror(YYLTYPE* locp, yyscan_t scanner, const char *format, ...)
{
	va_list alist;

	va_start(alist, format);
	yywarn(scanner, format, alist);
	va_end(alist);

	yyerror_reset(scanner);
}

void
yyperror(YYLTYPE* locp, yyscan_t scanner, const char *format, ...)
{
	va_list alist;

	va_start(alist, format);
	vwarn(format, alist);
	va_end(alist);

	yyerror_reset(scanner);
}

void
mdb_lex_state_yyerror(struct mdb_frame *fp, const char *format, ...)
{
	va_list alist;
	yyscan_t scanner = fp->f_lstate->scanner;

	va_start(alist, format);
	yywarn(scanner, format, alist);
	va_end(alist);

	yyerror_reset(scanner);
}

void
mdb_lex_state_yyperror(struct mdb_frame *fp, const char *format, ...)
{
	va_list alist;
	yyscan_t scanner = fp->f_lstate->scanner;

	va_start(alist, format);
	vwarn(format, alist);
	va_end(alist);

	yyerror_reset(scanner);
}

void
mdb_yyerror(const char *format, ...)
{
	va_list alist;
	mdb_frame_t *fp = mdb.m_frame;
	yyscan_t scanner = fp->f_lstate->scanner;

	va_start(alist, format);
	yywarn(scanner, format, alist);
	va_end(alist);

	yyerror_reset(scanner);
}

void
mdb_lex_state_set_yylineno(struct mdb_frame *fp, int lineno)
{
	yyscan_t scanner = fp->f_lstate->scanner;
	yyset_lineno(lineno, scanner);
}

int
mdb_lex_state_get_yylineno(struct mdb_frame *fp)
{
	yyscan_t scanner = fp->f_lstate->scanner;
	return (yyget_lineno(scanner));
}

static void
bkls_destroy(mdb_frame_t *fp)
{
	mdb_mblk_t *p;

	p = fp->f_lstate->allocs;
	while (p != NULL) {
		mdb_free(p->blk_addr, p->blk_size);
		fp->f_lstate->allocs = p->blk_next;
		mdb_free(p, sizeof (mdb_mblk_t));
		p = fp->f_lstate->allocs;
	}
}

static mdb_mblk_t *
blk_release(void *ptr, yyscan_t scanner)
{
	mdb_frame_t *fp;
	mdb_mblk_t *p;
	mdb_mblk_t **ppredp;

	if (ptr == NULL)
		return (NULL);

	fp = yyget_extra(scanner);
	ppredp = &fp->f_lstate->allocs;
	p = *ppredp;

	while ((p != NULL) && (p->blk_addr != ptr)) {
		ppredp = &p->blk_next;
		p = *ppredp;
	}

	if (p == NULL)
		return (NULL);

	*ppredp = p->blk_next;

	return (p);
}

static void
blk_push(mdb_mblk_t *p, yyscan_t scanner)
{
	mdb_frame_t *fp;

	fp = yyget_extra(scanner);
	p->blk_next = fp->f_lstate->allocs;
	fp->f_lstate->allocs = p;
}

void *
yyalloc(yy_size_t size, yyscan_t scanner)
{
	mdb_mblk_t *p;
	void *ptr;

	ptr = mdb_alloc(size, UM_SLEEP);
	if (ptr == NULL)
		return (NULL);

	p = (mdb_mblk_t *)mdb_alloc(sizeof (mdb_mblk_t), UM_SLEEP);
	p->blk_addr = ptr;
	p->blk_size = size;
	blk_push(p, scanner);

	return (ptr);
}

void *
yyrealloc(void *ptr, yy_size_t size, yyscan_t scanner)
{
	mdb_mblk_t *p;
	void *new_ptr;

	p = blk_release(ptr, scanner);
	if (p == NULL)
		return (yyalloc(size, scanner));

	new_ptr = mdb_alloc(size, UM_SLEEP);
	if (new_ptr == NULL) {
		mdb_free(p->blk_addr, p->blk_size);
		mdb_free(p, sizeof (mdb_mblk_t));
		return (NULL);
	}

	bcopy(p->blk_addr, new_ptr, size < p->blk_size ? size : p->blk_size);
	mdb_free(p->blk_addr, p->blk_size);
	p->blk_addr = new_ptr;
	p->blk_size = size;
	blk_push(p, scanner);

	return (new_ptr);
}

void
yyfree(void *ptr, yyscan_t scanner)
{
	mdb_mblk_t *p;

	p = blk_release(ptr, scanner);
	if (p != NULL) {
		mdb_free(p->blk_addr, p->blk_size);
		mdb_free(p, sizeof (mdb_mblk_t));
	}
}
